/* 
 * File:   AnimatedSprite.cpp
 * Copyright (C) 2008, 2009
 *		Sergio Padrino Recio (PadrinatoR)
 *		J. Daniel Expósito Polo (HellSpawn)
 *		Iván Villalba Sierra (Kasikiare)
 *
 * Created on 7 de septiembre de 2008, 19:25
 *
 * This file is part of BombProject.
 *
 * BombProject is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * BombProject is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with BombProject.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "AnimatedSprite.h"
#include "ConfigurationFile.h"
#include "ResourceManager.h"

/**
 * Constructor.
 */
AnimatedSprite::AnimatedSprite() : Sprite(NULL) {
	mAnimationSet = false;
}

/**
 * Destructor.
 */
AnimatedSprite::~AnimatedSprite() {
	destroy();
}

/**
 * Destroys everything that can be destroyed (within the sprite).
 */
void AnimatedSprite::destroy() {
	if (mOriginalSurface != NULL) {
		SDL_FreeSurface(mOriginalSurface);
		mOriginalSurface = NULL;
	}

	if (mTransformedSurface != NULL) {
		SDL_FreeSurface(mTransformedSurface);
		mTransformedSurface = NULL;
	}

	removeAllAnimations();
}

/**
 * Add a new animation from a file to be used with the sprite.
 * @param animation_name Name to identify the animation.
 * @param animation_file File name where the animation will be loaded from.
 * @param current Indicates whether the loaded animation must be set as the current one.
 */
void AnimatedSprite::addAnimation(string animation_name, string animation_file, bool current) {
	ConfigurationFile *reader = new ConfigurationFile();

	if (reader->loadFile(animation_file)) {
		Animation new_animation;

		string path = animation_file.substr(0, animation_file.rfind('/') + 1);
		new_animation.name = animation_name;
		new_animation.animation_surface = ResourceManager::getInstance()->loadImage(path + reader->getString("bitmap", ""));
		new_animation.frame_width = reader->getInteger("frame_width", 0);
		new_animation.frame_delay = reader->getInteger("frame_delay", 5);
		vector<int> new_hot_point = reader->getIntegerList("hotpoint");
		new_animation.hotpoint_x = new_hot_point[0];
		new_animation.hotpoint_y = new_hot_point[1];
		new_animation.frame_sequence = reader->getIntegerList("frame_list");

		mAnimations[animation_name] = new_animation;

		if (current)
			setAnimation(animation_name);
	}

	delete reader;
}

/**
 * Removes an animation from the animated sprite.
 * @param animation_name Name of the animation to be removed.
 */
void AnimatedSprite::removeAnimation(string animation_name) {
	if (mAnimations.find(animation_name) == mAnimations.end()) {
		fprintf(stderr, "AnimatedSprite::removeAnimation - Animation '%s' doesn't exist\n", animation_name.c_str());
	} else {
		ResourceManager::getInstance()->unloadImage(mAnimations[animation_name].animation_surface);
		mAnimations.erase(animation_name);

		if(mCurrentAnimationName == animation_name)
			mCurrentAnimationName = "";

		mAnimationSet = false;
	}
}

/**
 * Removes all the animations stored in the animated sprite.
 */
void AnimatedSprite::removeAllAnimations() {
	map<string, Animation>::iterator it;
	ResourceManager* resource_mgr = ResourceManager::getInstance();

	for (it = mAnimations.begin(); it != mAnimations.end(); ++it) {
		Animation anim = it->second;
		resource_mgr->unloadImage(anim.animation_surface);
	}

	mAnimations.clear();

	mCurrentAnimationName = "";
	
	mAnimationSet = false;
}

/**
 * Sets an animation as the current animation.
 * @param animation_name Name of the animation to be set.
 */
void AnimatedSprite::setAnimation(string animation_name) {
	if(animation_name == mCurrentAnimationName)
		return;

	if (mAnimations.find(animation_name) == mAnimations.end()) {
		fprintf(stderr, "AnimatedSprite::setAnimation - Animation '%s' doesn't exist.\n", animation_name.c_str());

		return;
	}

	mCurrentAnimationName = animation_name;
	mCurrentAnimation = mAnimations[animation_name];

	if (mOriginalSurface != NULL)
		SDL_FreeSurface(mOriginalSurface);

	mOriginalSurface = SDL_CreateRGBSurface(mCurrentAnimation.animation_surface->flags,
			mCurrentAnimation.frame_width,
			mCurrentAnimation.animation_surface->h,
			mCurrentAnimation.animation_surface->format->BitsPerPixel,
			mCurrentAnimation.animation_surface->format->Rmask,
			mCurrentAnimation.animation_surface->format->Gmask,
			mCurrentAnimation.animation_surface->format->Bmask,
			mCurrentAnimation.animation_surface->format->Amask);

	//Sets the colorkey for the new surface
	SDL_SetColorKey(mOriginalSurface, SDL_SRCCOLORKEY, SDL_MapRGB(mOriginalSurface->format, 0xFF, 0, 0xFF));

	mImageWidth = mOriginalSurface->w;
	mImageHeight = mOriginalSurface->h;

	setHotPoint(mCurrentAnimation.hotpoint_x, mCurrentAnimation.hotpoint_y);

	setFrame(0);

	mAnimationState = ANIMATION_STOPPED;

	mAnimationSet = true;
	mFinished = false;
}

/**
 * Sets the current frame of the animation, and stores it in a special surface.
 * @param num_frame Number of the new frame to be set.
 */
void AnimatedSprite::setFrame(int num_frame) {
	mCurrentFrame = num_frame;

	SDL_Rect frame_rect;
	frame_rect.x = mCurrentAnimation.frame_sequence[mCurrentFrame] * mCurrentAnimation.frame_width;
	frame_rect.y = 0;
	frame_rect.w = mCurrentAnimation.frame_width;
	frame_rect.h = mCurrentAnimation.animation_surface->clip_rect.h;

	SDL_BlitSurface(mCurrentAnimation.animation_surface, &frame_rect, mOriginalSurface, NULL);

	notifyChange();
}

/**
 * Inits the sprite's animation.
 * @param flags Animation flags. <i>Default: ANIMATION_NORMAL | ANIMATION_LOOP</i>
 * @param times Times to play the animation. <i>Default: 1</i>
 */
void AnimatedSprite::playAnimation(Uint16 flags, Sint16 times) {
	if (!mAnimationSet) {
		fprintf(stderr, "AnimatedSPrite::Play - No animation was set!\n");
		return;
	}

	mFlags = (AnimationFlags) flags;
	mTimes = times;

	if (mFlags & ANIMATION_REVERT) {
		mFrameSpeed = -1;
		setFrame(mCurrentAnimation.frame_sequence.size() - 1);
	} else {
		mFrameSpeed = 1;
		setFrame(0);
	}

	mFrameCounter = mTimesCounter = 0;
	mFinished = false;
	mAnimationState = ANIMATION_PLAYING;
}

/**
 * Pauses the sprite's animation.
 */
void AnimatedSprite::pauseAnimation() {
	if (mAnimationState != ANIMATION_PLAYING) {
		fprintf(stderr, "AnimatedSPrite::Pause - Sprites cannot be paused if they're stopped!\n");
		return;
	}

	mAnimationState = ANIMATION_PAUSED;
}

/**
 * Stops the sprite's animation.
 */
void AnimatedSprite::stopAnimation() {
	if (mAnimationSet == ANIMATION_STOPPED) {
		fprintf(stderr, "AnimatedSPrite::Stop - Sprites cannot be stopped if they're already stopped!\n");
		return;
	}

	if (mFlags & ANIMATION_REVERT)
		setFrame(mCurrentAnimation.frame_sequence.size() - 1);
	else
		setFrame(0);

	mAnimationState = ANIMATION_STOPPED;
}

/**
 * Resumes the sprite's animation.
 */
void AnimatedSprite::resumeAnimation() {
	if (mAnimationState == ANIMATION_PLAYING) {
		fprintf(stderr, "AnimatedSPrite::Resume - Sprites cannot be resumed if they aren't paused!\n");
		return;
	}

	mAnimationState = ANIMATION_PLAYING;
}

/**
 * Handles the timing of the current animation.
 */
void AnimatedSprite::updateAnimation() {
	//TODO: avoid updating when the animation has only one frame. BE CAREFUL: when an animation is added, sometimes it's not set as current animation, so no frame is in the sprite surface. Thus it's necessary to update the sprite surface once.
	if (!mAnimationSet) {
		fprintf(stderr, "AnimatedSprite::Update - Nothing can be updated if there is not an animation set.\n");
		return;
	}

	if (mAnimationState != ANIMATION_PLAYING) return;

	mFinished = false;
	mFrameCounter++;

	if (mFrameCounter <= mCurrentAnimation.frame_delay)
		return;

	mFrameCounter = 0;

	mCurrentFrame += mFrameSpeed;

	if (mFrameSpeed > 0) {
		if (mCurrentFrame >= mCurrentAnimation.frame_sequence.size()) {

			if (mFlags & ANIMATION_PING_PONG) {
				mFrameSpeed = -mFrameSpeed;
				mFrameCounter = mCurrentAnimation.frame_delay + 1;
				mCurrentFrame--;

				return;
			}

			setFrame(0);

			if (mFlags & ANIMATION_N_TIMES) {
				mTimesCounter++;

				if (mTimesCounter == mTimes) {
					stopAnimation();
					mFinished = true;
				}
			}
		} else {
			setFrame(mCurrentFrame);
		}
	} else {
		if (mCurrentFrame < 0) {

			if (mFlags & ANIMATION_PING_PONG) {
				mFrameSpeed = -mFrameSpeed;
				mFrameCounter = mCurrentAnimation.frame_delay + 1;
				mCurrentFrame = 0;
			} else
				setFrame(mCurrentAnimation.frame_sequence.size() - 1);

			if (mFlags & ANIMATION_N_TIMES) {
				mTimesCounter++;

				if (mTimesCounter == mTimes) {
					stopAnimation();
					mFinished = true;
				}
			}
		} else {
			setFrame(mCurrentFrame);
		}
	}
}

/**
 * Indicates if the animation has been finished.
 * @return true if the animation has been finished.
 */
bool AnimatedSprite::hasFinished() {
	if (!mAnimationSet) {
		fprintf(stderr, "AnimatedSprite::hasFinished - Impossible get property finished if not Animation is set.\n");
		return false;
	}
	return mFinished;
}
